package com.alibaba.fastjson2.issues_2500;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class Issue2548A {
    @Test
    public void fastJson() {
        StoreDTO storeDTO = new StoreDTO();
        BananaDTO banana = new BananaDTO();
        banana.setCount(BigDecimal.valueOf(10));
        storeDTO.setItem(banana);

        String jsonString = JSON.toJSONString(storeDTO, JSONWriter.Feature.WriteNulls);
        assertEquals("{\"item\":{\"kind\":\"banana\",\"count\":10},\"storeId\":null}", jsonString);

        StoreDTO store = JSON.parseObject(jsonString, StoreDTO.class);

        assertEquals("banana", store.getItem().getKind());
    }

    @Test
    public void jackson() throws Exception {
        //given
        ObjectMapper objectMapper = new ObjectMapper();

        StoreDTO storeDTO = new StoreDTO();
        BananaDTO banana = new BananaDTO();
        banana.setCount(BigDecimal.valueOf(10));
        storeDTO.setItem(banana);

        //when
        String jsonString = objectMapper.writeValueAsString(storeDTO);
        StoreDTO store = objectMapper.readValue(jsonString, StoreDTO.class);

        //then
        assertEquals("banana", store.getItem().getKind());
    }

    @JsonTypeName("store")
    public static class StoreDTO
            implements Serializable {
        private static final long serialVersionUID = 1L;

        private String storeId;

        private FruitDTO item;

        public StoreDTO storeId(String storeId) {
            this.storeId = storeId;
            return this;
        }

        /**
         * The unique ID of the fruit
         * @return storeId
         */

        @JsonProperty("storeId")
        public String getStoreId() {
            return storeId;
        }

        public void setStoreId(String storeId) {
            this.storeId = storeId;
        }

        public StoreDTO item(FruitDTO item) {
            this.item = item;
            return this;
        }

        /**
         * Get item
         * @return item
         */
        @JsonProperty("item")
        public FruitDTO getItem() {
            return item;
        }

        public void setItem(FruitDTO item) {
            this.item = item;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            StoreDTO store = (StoreDTO) o;
            return Objects.equals(this.storeId, store.storeId) &&
                    Objects.equals(this.item, store.item);
        }

        @Override
        public int hashCode() {
            return Objects.hash(storeId, item);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("class StoreDTO {\n");
            sb.append("    storeId: ").append(toIndentedString(storeId)).append("\n");
            sb.append("    item: ").append(toIndentedString(item)).append("\n");
            sb.append("}");
            return sb.toString();
        }

        /**
         * Convert the given object to string with each line indented by 4 spaces
         * (except the first line).
         */
        private String toIndentedString(Object o) {
            if (o == null) {
                return "null";
            }
            return o.toString().replace("\n", "\n    ");
        }
    }

    @JsonIgnoreProperties(
            value = "kind", // ignore manually set kind, it will be automatically generated by Jackson during serialization
            allowSetters = true // allows the kind to be set during deserialization
    )
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "kind", visible = true)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = AppleDTO.class, name = "apple"),
            @JsonSubTypes.Type(value = BananaDTO.class, name = "banana")
    })
    public interface FruitDTO
            extends Serializable {
        public String getKind();
    }

    @JsonTypeName("banana")
    public static class BananaDTO
            implements Serializable, FruitDTO {
        private static final long serialVersionUID = 1L;

        private String kind;

        private BigDecimal count;

        public BananaDTO kind(String kind) {
            this.kind = kind;
            return this;
        }

        /**
         * Get kind
         * @return kind
         */

        @JsonProperty("kind")
        public String getKind() {
            return kind;
        }

        public void setKind(String kind) {
            this.kind = kind;
        }

        public BananaDTO count(BigDecimal count) {
            this.count = count;
            return this;
        }

        /**
         * Get count
         * @return count
         */
        @JsonProperty("count")
        public BigDecimal getCount() {
            return count;
        }

        public void setCount(BigDecimal count) {
            this.count = count;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            BananaDTO banana = (BananaDTO) o;
            return Objects.equals(this.kind, banana.kind) &&
                    Objects.equals(this.count, banana.count);
        }

        @Override
        public int hashCode() {
            return Objects.hash(kind, count);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("class BananaDTO {\n");
            sb.append("    kind: ").append(toIndentedString(kind)).append("\n");
            sb.append("    count: ").append(toIndentedString(count)).append("\n");
            sb.append("}");
            return sb.toString();
        }

        /**
         * Convert the given object to string with each line indented by 4 spaces
         * (except the first line).
         */
        private String toIndentedString(Object o) {
            if (o == null) {
                return "null";
            }
            return o.toString().replace("\n", "\n    ");
        }
    }

    @JsonTypeName("apple")
    public static class AppleDTO
            implements Serializable, FruitDTO {
        private String kind;

        @Override
        public String getKind() {
            return kind;
        }

        public void setKind(String kind) {
            this.kind = kind;
        }
    }
}
